---
title: "Cloudflare"
description: Deploy Hot Updater server to Cloudflare Workers with D1 and R2.
icon: cloudflare
version: "v0.22.0"
---

## Prerequisites

- Cloudflare account at [cloudflare.com](https://cloudflare.com)
- Node.js 20+ installed locally

## Installation

Install required dependencies.

```package-install
npm install @hot-updater/server @hot-updater/aws hono
```

Install Drizzle ORM with D1 adapter.

```package-install
npm install drizzle-orm
```

```package-install
npm install -D wrangler drizzle-kit @cloudflare/workers-types
```

## Cloudflare Resources

Create a D1 database.

```bash
npx wrangler d1 create hot-updater-db
```

Create an R2 bucket for bundle storage.

```bash
npx wrangler r2 bucket create hot-updater-bundles
```

## Wrangler Configuration

Create `wrangler.toml` with D1 binding.

```toml
name = "hot-updater-server"
main = "src/index.ts"
compatibility_date = "2024-01-01"

[[d1_databases]]
binding = "DB"
database_name = "hot-updater-db"
database_id = "your-database-id-here"
```

Replace `database_id` with the ID from D1 creation output.

## Environment Variables

Set R2 credentials as secrets using Wrangler.

```bash
npx wrangler secret put R2_ACCOUNT_ID
npx wrangler secret put R2_ACCESS_KEY_ID
npx wrangler secret put R2_SECRET_ACCESS_KEY
```

Generate R2 API tokens from Cloudflare dashboard → R2 → Manage R2 API Tokens.

## Database Setup

Create the database connection file.

```typescript title="src/drizzle.ts"
import { drizzle } from "drizzle-orm/d1";

export interface Env {
  DB: D1Database;
  R2_ACCOUNT_ID: string;
  R2_ACCESS_KEY_ID: string;
  R2_SECRET_ACCESS_KEY: string;
}

export const getDb = (env: Env) => drizzle(env.DB);
```

## Hot Updater Configuration

Create the Hot Updater instance.

```typescript title="src/hotUpdater.ts"
import { createHotUpdater } from "@hot-updater/server";
import { drizzleAdapter } from "@hot-updater/server/adapters/drizzle";
import { s3Storage } from "@hot-updater/aws";
import { Env, getDb } from "./drizzle";

export const getHotUpdater = (env: Env) =>
  createHotUpdater({
    database: drizzleAdapter({ db: getDb(env), provider: "sqlite" }),
    storages: [
      s3Storage({
        region: "auto",
        endpoint: `https://${env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
        credentials: {
          accessKeyId: env.R2_ACCESS_KEY_ID,
          secretAccessKey: env.R2_SECRET_ACCESS_KEY,
        },
        bucketName: "hot-updater-bundles",
      }),
    ],
    basePath: "/hot-updater",
  });
```

## Worker Entry Point

Create the Cloudflare Worker with Hono.

```typescript title="src/index.ts"
import { Hono } from "hono";
import { Env } from "./drizzle";
import { getHotUpdater } from "./hotUpdater";

const app = new Hono<{ Bindings: Env }>();

app.on(["POST", "GET", "DELETE"], "/hot-updater/*", async (c) => {
  const hotUpdater = getHotUpdater(c.env);
  return hotUpdater.handler(c.req.raw);
});

export default app;
```

## Schema Generation

Generate the Drizzle schema for Hot Updater tables.

```bash
npx hot-updater db generate src/hotUpdater.ts --yes
```

## Database Migration

Apply migrations to D1 database.

```bash
npx wrangler d1 migrations apply hot-updater-db
```

## R2 Public Access

Configure R2 custom domain for public bundle access:

1. Go to Cloudflare dashboard → R2 → your bucket → Settings
2. Add a custom domain (e.g., `bundles.yourdomain.com`)
3. R2 bundles are publicly accessible via the S3-compatible API

## Development

Run the development server locally.

```bash
npx wrangler dev
```

Server runs at `http://localhost:8787`.

## Deploy

Deploy to Cloudflare Workers.

```bash
npx wrangler deploy
```

Your Hot Updater server will be available at `https://hot-updater-server.your-subdomain.workers.dev/hot-updater`.

## CLI Configuration

Configure your CLI to use this deployed server.

```typescript title="hot-updater.config.ts"
import { defineConfig } from "@hot-updater/core";
import { bare } from "@hot-updater/bare";
import { r2Storage } from "@hot-updater/cloudflare";
import { standaloneRepository } from "@hot-updater/standalone";

export default defineConfig({
  build: bare(),
  storage: r2Storage({
    accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
    accessKeyId: process.env.R2_ACCESS_KEY_ID!,
    secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
    bucketName: "hot-updater-bundles",
    publicUrl: "https://bundles.yourdomain.com",
  }),
  database: standaloneRepository({
    baseUrl: "https://hot-updater-server.your-subdomain.workers.dev/hot-updater",
  }),
});
```

<Callout type="warn">
**R2 Storage Configuration**

The server uses `s3Storage` from `@hot-updater/aws` because `r2Storage` does not support `getDownloadUrl()`, which is required for generating presigned download URLs.

The CLI uses `r2Storage` from `@hot-updater/cloudflare` (via Wrangler CLI) for deployment, while the server uses `s3Storage` (via S3-compatible API) for serving bundles. Both access the same R2 bucket.

Make sure both configurations point to the same R2 bucket name.
</Callout>

## Next Steps

- Set up [custom domains](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/) for Workers
- Enable [analytics](https://developers.cloudflare.com/workers/observability/analytics/) for monitoring
- Configure [R2 caching](https://developers.cloudflare.com/r2/buckets/public-buckets/) for performance
